在我們認識了函式後,今天要討論的是在使用陳述式跟函式時必須注意的一個細節,作用域 (Scope)。作用域指的是被命名的實體 (entity),一般指能儲存資料的變數,在程式中被視為有效,即能被存取的範圍及位置。
昨天我們曾提到函式的開始與結束位置之間的區域則被稱為區塊 (Block),但區塊本身並不只限於形容函式的範圍,也會包含陳述式、類別等一切程式中能被規劃出範圍的部分,都會被視為區塊。區塊與區塊之間是互相獨立的,也就是它們的資料是沒辦法共通使用的。舉例來說,如果我們在主函式中宣告了一個變數 n,在主函式區塊以外的函式 getSum 內是沒辦法存取到變數 n,這是因為對於函式 getSum 來說,變數 sum 並不存在於區塊中,而我們沒辦法存取一個不存在的變數,因此我們才會需要透過參數來把主函式區塊的資料傳到函式的區塊中。當然,反之亦然,我們也不可以在主函式中使用函式 getSum 中的變數 sum,因此我們會使用回傳陳述式來把函式的區塊中的資料傳到主函式區塊中。而這個決定變數能否存取的規則,便是作用域。
區塊的例子:[C#]
using System;
public class ScopeExample
{
// public static int getSum(int n) { <-- 使用參數來獲取區塊外的資料
public static int getSum() {
// 函式 getSum 區塊開始
int sum = 0;
// for 迴圈區塊開始
for (int i = 1; i <= n; i++) { // 錯誤! 變數 n 並不存在於區塊中
sum += i;
}
// for 迴圈區塊結束
return sum;
// 函式 getSum 區塊結束
}
public static void Main(string[] args)
{
// 主函式區塊開始
int n = 10;
Console.WriteLine(getSum());
// 主函式區塊結束
}
}
等一等,剛剛說到陳述式的範圍也是區塊的一種,而區塊與區塊之間是互相獨立,那為什麼在上面的例子中,我們在 for 迴圈區塊中能使用變數 sum 呢?這是因為它是函式區塊內的子區塊,也就是說它也屬於函式區塊內的一部分,因此它能使用函式區塊中的變數 sum。不過,需要注意的是,函式區塊卻不算是它的子區塊內的一部分,因此我們沒辦法在函式區塊內中存取 for 迴圈區塊中宣告及建立的變數,如上面的例子中,函式 getSum 並不能存取到 for 迴圈區塊中的變數 i。
我們發現,在函式區塊中宣告變數會讓它沒辦法在其他函式區塊中被使用,那如果我們把宣告變數的動作放到函式區塊外又如何呢?答案是所有的函式區塊中都能使用該變數,這是因為程式本身也是一個區塊,而程式區塊中的所有函式區塊都會是它的子區塊,因此就跟陳述式區塊的邏輯一樣,屬於子區塊的函式區塊能使用作為父區塊的程式區塊中的變數。不過我們還是會把兩者作出一點區分,在函式區塊中宣告的變數被稱為區域變數 (Local variables);而在函式區塊外的程式區塊中宣告的變數則被稱為全域變數 (Global variables)。這是因為它們在程式中儲存的方式跟位置不一樣,因此我們可以同時存在相同名稱的全域變數跟區域變數,卻不可以同時存在兩個相同名稱的區域變數。
以下是上面的例子使用全域變數後的樣子:[Java]
public class ScopeExample {
static int n = 10; // 宣告全域變數 n
static int sum = 0; // 宣告全域變數 sum
public static void getSum() {
for (int i = 1; i <= n; i++) {
sum += i;
}
}
public static void main(String args[]) {
System.out.println(sum); // 顯示為 0
getSum(); // 使用函式 getSum 來改變全域變數 sum
System.out.println(sum); // 顯示為 55
}
}
不過,大家需要注意的是,假如在一個區塊同時存在相同名稱的全域變數跟區域變數時,程式會優先存取區域變數,以下為例子:[Java]
public class ScopeExample {
static int n = 10; // 宣告全域變數 n
static int sum = 0; // 宣告全域變數 sum
public static void getSum() {
int sum = 0; // 宣告區域變數 sum
for (int i = 1; i <= n; i++) {
sum += i; // 這裡使用的是區域變數 sum 而不是全域變數 sum,因此全域變數 sum 並沒有任何的改變
}
}
public static void main(String args[]) {
System.out.println(sum); // 顯示為 0
getSum(); // 使用函式 getSum
System.out.println(sum); // 顯示還是為 0
}
}
既然全域變數比區域變數方便那麼多,我們都使用全域變數不就好了。不過,實際上卻不一定,當程式變得複雜時,隨意地使用全域變數反而有機會因為在某個區塊中對被當作區域變數使用的全域變數進行臨時改動後導致程式的其他部分出現錯誤。因此,一般都會建議儘可能多使用區域變數而不是全域變數,不過常數因為不會在程式執行時有任何的改動,便很常會以全域變數的方式宣告使用。